package org.fhm.zdte.protocol.zrudp.service;

import org.fhm.substrate.annotation.Component;
import org.fhm.substrate.annotation.Setup;
import org.fhm.substrate.manager.Bootstrap;
import org.fhm.substrate.service.LoggerHandler;
import org.fhm.substrate.standard.ILogger;
import org.fhm.zdte.common.constant.protocol.ContentType;
import org.fhm.zdte.common.constant.protocol.PacketType;
import org.fhm.zdte.common.constant.protocol.ResponseMessageType;
import org.fhm.zdte.common.constant.subcmd.ConnectCmd;
import org.fhm.zdte.common.constant.subcmd.RequestCmd;
import org.fhm.zdte.common.exception.ProtocolException;
import org.fhm.zdte.common.pojo.Address;
import org.fhm.zdte.common.protocol.AbstractMessageStandard;
import org.fhm.zdte.common.standard.IJsonConvertor;
import org.fhm.zdte.common.standard.ISubCmd;
import org.fhm.zdte.common.util.CommonUtil;
import org.fhm.zdte.protocol.zrudp.constant.DatagramHead;
import org.fhm.zdte.protocol.zrudp.standard.IProtocolMessageConvertor;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * Common convertor is used to covert Packet-Message-Member to Byte-Array.
 *
 * @author 谭波
 * @since 2024/2/2
 */
@Component
public class CommonProtocolMessageConvertor implements IProtocolMessageConvertor {

    private final ILogger logger = LoggerHandler.getLogger(CommonProtocolMessageConvertor.class);

    private final Byte[] HEAD_SEPARATOR = new Byte[]{' ', '\n', ' '};

    private final BiFunction<byte[], Integer, Boolean> headSeparatorMatcher
            = (k, i) -> k[i] == HEAD_SEPARATOR[0] && k[i + 1] == HEAD_SEPARATOR[1] && k[i + 2] == HEAD_SEPARATOR[2];

    private final ThreadLocal<MessageMemberToByteArray> m2b = new ThreadLocal<>();

    private final ThreadLocal<ByteArrayToMessageMember> b2m = new ThreadLocal<>();

    @Setup
    private CommonUtil commonUtil;

    @Setup
    private IJsonConvertor iJsonConvertor;

    @Override
    public MessageMemberToByteArray ofMessageMemberToByteArray() {
        return commonUtil.obtainObjOfThreadLocal(m2b, () -> new MessageMemberToByteArray(iJsonConvertor));
    }

    @Override
    public ByteArrayToMessageMember ofByteArrayToMessage() {
        return commonUtil.obtainObjOfThreadLocal(b2m, () -> new ByteArrayToMessageMember(iJsonConvertor));
    }

    public BiFunction<byte[], Integer, Boolean> getHeadSeparatorMatcher() {
        return headSeparatorMatcher;
    }

    public static class ByteArrayToMessageMember {

        private final ILogger logger = LoggerHandler.getLogger(ByteArrayToMessageMember.class);

        private final Map<Class<?>, Function<List<Byte>, Object>> converterMap = new HashMap<>();
        private final IJsonConvertor iJsonConvertor;
        private Byte subCmdBit;
        private Field subCmdField;
        private AbstractMessageStandard message;

        public ByteArrayToMessageMember(IJsonConvertor iJsonConvertor) {
            this.iJsonConvertor = iJsonConvertor;
        }

        public ByteArrayToMessageMember prepare(AbstractMessageStandard message) {
            converterMap.put(
                    String.class,
                    bytesList -> new String(list2ByteArr(bytesList), Bootstrap.charset)
            );
            converterMap.put(
                    Address.class,
                    bytesList -> iJsonConvertor.fromJson(new String(list2ByteArr(bytesList), Bootstrap.charset), Address.class)
            );
            converterMap.put(Byte.class, bytesList -> bytesList.get(0));
            converterMap.put(
                    Integer.class,
                    bytesList -> Integer.parseInt(new String(list2ByteArr(bytesList), Bootstrap.charset), 16)
            );
            converterMap.put(byte[].class, this::list2ByteArr);
            converterMap.put(
                    PacketType.class,
                    bytesList -> PacketType.getPacketType(bytesList.get(0))
            );
            converterMap.put(
                    ResponseMessageType.class,
                    bytesList -> ResponseMessageType.getResponseMessageType(bytesList.get(0))
            );
            converterMap.put(
                    ContentType.class,
                    bytesList -> ContentType.getContentType(bytesList.get(0))
            );
            this.message = message;
            return this;
        }

        public ByteArrayToMessageMember preAssemble(
                String fieldName, List<Byte> v
        ) {
            try {
                Field field = message.getClass().getDeclaredField(fieldName);
                Class<?> type = field.getType();
                Function<List<Byte>, Object> function = converterMap.get(type);
                field.setAccessible(true);
                if (Objects.isNull(function)) {
                    if (ISubCmd.class.isAssignableFrom(type)) {
                        subCmdBit = v.get(0);
                        subCmdField = field;
                        return this;
                    } else {
                        logger.warn("the type {} has not match ByteArrayToMessageMember-Convertor", type);
                    }
                }
                field.set(message, function.apply(v));
            } catch (IllegalAccessException | NoSuchFieldException e) {
                logger.error("the member {} of message body converter fail", fieldName);
            }
            return this;
        }

        public void afterAssemble() {
            PacketType packetType = message.getPacketType();
            if (PacketType.CONNECT.equals(packetType))
                assemble(ConnectCmd.obtainSubCmd(subCmdBit));
            if (PacketType.REQUEST.equals(packetType))
                assemble(RequestCmd.obtainSubCmd(subCmdBit));
        }

        private void assemble(ISubCmd iSubCmd) {
            if (Objects.nonNull(iSubCmd))
                try {
                    subCmdField.set(message, iSubCmd);
                } catch (IllegalAccessException e) {
                    logger.error("sub cmd assemble fail", e);
                }
        }

        private byte[] list2ByteArr(List<Byte> collection) {
            if (Objects.isNull(collection))
                return new byte[0];
            int len;
            byte[] bytes = new byte[(len = collection.size())];
            for (int i = 0; i < len; i++) {
                Byte b;
                if (Objects.nonNull((b = collection.get(i))))
                    bytes[i] = b;
            }
            return bytes;
        }

    }

    public class MessageMemberToByteArray {

        private final Map<Class<?>, Function<Object, byte[]>> converterMap = new HashMap<>();
        private final IJsonConvertor iJsonConvertor;
        private DatagramHead head;
        private Object member;
        private Field field;
        private byte[] messageValue;

        public MessageMemberToByteArray(IJsonConvertor iJsonConvertor) {
            this.iJsonConvertor = iJsonConvertor;
        }

        public MessageMemberToByteArray prepare(DatagramHead head) {
            converterMap.put(ISubCmd.class, obj -> new byte[]{((ISubCmd) obj).getFlagOfSubCmd()});
            converterMap.put(PacketType.class, obj -> new byte[]{((PacketType) obj).getValueBit()});
            converterMap.put(ContentType.class, obj -> new byte[]{((ContentType) obj).getValueBit()});
            converterMap.put(ResponseMessageType.class, obj -> new byte[]{((ResponseMessageType) obj).getValueBit()});
            converterMap.put(byte[].class, obj -> (byte[]) obj);
            converterMap.put(String.class, obj -> ((String) obj).getBytes(Bootstrap.charset));
            converterMap.put(Byte.class, obj -> new byte[]{(byte) obj});
            converterMap.put(Address.class, obj -> iJsonConvertor.toJson(obj).getBytes(Bootstrap.charset));
            converterMap.put(Integer.class, obj -> {
                String hexString = Integer.toHexString((Integer) obj);
                if (hexString.matches("[0-9A-Fa-f]+")) {
                    int count;
                    StringBuilder buffer = new StringBuilder();
                    if ((count = 7 - hexString.length()) > 0) {
                        for (int i = 0; i < count; i++) {
                            buffer.append("0");
                        }
                    }
                    buffer.append(hexString);
                    return buffer.toString().getBytes(Bootstrap.charset);
                }
                throw new ProtocolException("illegal string transform");
            });
            this.head = head;
            return this;
        }

        public MessageMemberToByteArray obtainMember(AbstractMessageStandard message) {
            try {
                field = message.getClass().getDeclaredField(head.getFieldName());
                field.setAccessible(true);
                member = field.get(message);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            return this;
        }

        public MessageMemberToByteArray convert() {
            Function<Object, byte[]> converter;
            if (Objects.nonNull(member)) {
                Class<?> type = field.getType();
                if (Objects.nonNull((converter = converterMap.get(type)))) {
                    messageValue = converter.apply(member);
                    return this;
                } else {
                    logger.warn("the type {} has not match MessageMemberToByteArray-Convertor", type);
                }
            }
            return this;
        }

        public void addHeadSeparator(List<Byte> stream) {
            if (Objects.nonNull(member)) {
                stream.add(head.getValueBit());
                List<Byte> list = new ArrayList<>();
                for (byte b : messageValue) {
                    list.add(b);
                }
                stream.addAll(list);
                stream.addAll(Arrays.asList(HEAD_SEPARATOR));
            }
        }

    }
}
